import json
from time import time
import hashlib
from urllib.parse import urlparse
import requests
from textwrap import dedent
from uuid import uuid4
from flask import Flask, jsonify, request


class Blockchain(object):
    def __init__(self):
        self.chain = []
        self.current_transactions = []  # 交易数据
        self.new_block(proof=100, previous_hash=1)  # 设置默认区块
        self.nodes = set()

    # 注册新的节点
    def register_node(self, address):
        parsed_url = urlparse(address)  # 截取有效url
        self.nodes.add(parsed_url.netloc)  # 有效的URL保存到set集合中

    # 解决节点冲突
    def resolve_conflicts(self):
        neighbours = self.nodes
        new_chain = None
        # 最长的链
        max_length = len(self.chain)
        # 获取验证所有网络节点
        for node in neighbours:
            response = requests.get(f'http://{node}/chain')
            print(response)
            exit()
            if response.status_code == 200:
                length = response.json()['length']
                chain = response.json()['chain']
                # 检查长度是否较长，链条是否有效
                if length > max_length and self.valid_chain(chain):
                    max_length = length  # 赋值最新的长度
                    new_chain = chain  # 赋值最新的链
        # 发现比我们长 并且有效的链 取代我们的chain
        if new_chain:
            self.chain = new_chain
            return True

        return False

    # 验证 chain
    def valid_chain(self, chain):
        # 判断链链是否有效
        last_block = chain[0]  # 最后的区块
        current_index = 1  # 现在的索引长度
        # 循环验证链
        while current_index < len(chain):
            block = chain[current_index]
            print(f'{last_block}')
            print(f'{block}')
            print('\n-----------\n')
            # 检查hash值是否正确
            if block['previous_hash'] != self.hash(last_block):
                return False
            # 检查工作证明是否正确
            if not self.valid_proof(last_block['proof'], block['proof']):
                return False
            last_block = block
            current_index += 1
        return True

    # 新的交易
    def new_transaction(self, sender, recipient, amount):
        # 生成新交易信息，信息将加入到下一个待挖的区块中
        self.current_transactions.append(
            {
                'sender': sender,  # 付款人
                'recipient': recipient,  # 收款人
                'amount': amount,  # 数量
            }
        )
        # 信息加入下一个区块中
        return self.chain[-1]['index'] + 1

    # 区块
    def new_block(self, proof, previous_hash=None):
        # 生成新的区块
        block = {
            'index': len(self.chain) + 1,  # 区块索引
            'timestamp': time(),  # 时间
            'transactions': self.current_transactions,  # 交易数据
            'proof': proof,  # 证明
            'previous_hash': previous_hash or self.hash(self.chain[-1])  # hash值
        }
        # 重置当前事务链
        self.current_transactions = []
        self.chain.append(block)
        return block

    # 获取上一个区块
    def last_block(self):
        return self.chain[-1]

    # hash结果
    def hash(self, block):
        # 生成区块的hash值 256()
        # block json
        block_string = json.dumps(block, sort_keys=True).encode()
        # 返回区块哈希值结果
        return hashlib.sha256(block_string).hexdigest()

    # 证明
    def proof_of_work(self, last_proof):
        # 工作证明 p是上一个区块的证明
        proof = 0
        times = time()
        while self.valid_proof(last_proof, proof) is False:
            proof += 1
        return proof

    # 验证证明
    def valid_proof(self, last_proof, proof):
        guess = f'{last_proof}{proof}'.encode()
        guess_hash = hashlib.sha256(guess).hexdigest()
        print(guess_hash)
        return guess_hash[:4] == '0000'


# 区块名称
app = Flask(__name__)
# 区块唯一标识
node_identifier = str(uuid4()).replace('-', '')
# 实例化对象
blockchain = Blockchain()


@app.route('/mine', methods=['GET'])
def mine():
    # 运算的工作证明将会到下一个区块证明
    last_block = blockchain.last_block()
    last_proof = last_block['proof']  # 获取上一个区块的证明
    proof = blockchain.proof_of_work(last_proof)  # 验证上一个区块证明
    # 给工作量证明的节点提供奖励
    # sender 表示新挖出的币
    # recipient 给出区块唯一标识
    # amount 奖励币的个数
    blockchain.new_transaction(sender=0, recipient=node_identifier, amount=1)
    # 将新的块添加到链中来生成新的块
    block = blockchain.new_block(proof)
    # 产生新的区块
    response = {
        'message': '新区块生成中',
        'index': block['index'],
        'transactions': block['transactions'],
        'proof': block['proof'],
        'previous_hash': block['previous_hash']
    }
    return jsonify(response), 200


@app.route('/transactions/new', methods=['POST'])
def new_transaction():
    # 新的区块交易
    values = request.get_json()
    required = ['sender', 'recipient', 'amount']
    if not all(k in values for k in required):
        return '参数遗漏', 400
    index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
    response = {'message': f'交易数据将会添加到下一个区块中{index}'}
    return jsonify(response), 201


@app.route('/chain', methods=['GET'])
def full_chain():
    response = {
        'chain': blockchain.chain,
        'lengh': len(blockchain.chain),
    }
    return jsonify(response), 200


# 注册节点接口
@app.route('/node/register', methods=['POST'])
def register_nodes():
    value = request.get_json()
    nodes = value.get('nodes')
    if nodes is None:
        return '请提供有效的节点列表', 400
    for node in nodes:
        blockchain.register_node(node)
    response = {
        'message': '有新的节点注册',
        'total_nodes': list(blockchain.nodes)
    }
    # 返回注册的节点
    return jsonify(response), 201


@app.route('/nodes/resolve', methods=['GET'])
def consensus():
    replaced = blockchain.resolve_conflicts()
    if replaced:
        response = {
            'message': 'chain to  new ',
            'new_chain': blockchain.chain
        }
    else:
        response = {
            'message': '我们的链是严谨的',
            'new_chain': blockchain.chain
        }
    return jsonify(response), 200


if __name__ == '__main__':
    print(app.run(host='172.31.155.192', port=5000, debug=True))
    # blockchain.register_node('https://127.0.0.1/tex')
